/*
给你一个 不含重复 单词的字符串数组 words ，请你找出并返回 words 中的所有 连接词 。

连接词 定义为：一个完全由给定数组中的至少两个较短单词组成的字符串。

示例 1：

输入：words = ["cat","cats","catsdogcats","dog","dogcatsdog","hippopotamuses","rat","ratcatdogcat"]
输出：["catsdogcats","dogcatsdog","ratcatdogcat"]
解释："catsdogcats" 由 "cats", "dog" 和 "cats" 组成; 
     "dogcatsdog" 由 "dog", "cats" 和 "dog" 组成; 
     "ratcatdogcat" 由 "rat", "cat", "dog" 和 "cat" 组成。
示例 2：

输入：words = ["cat","dog","catdog"]
输出：["catdog"]
 

提示：

1 <= words.length <= 104
0 <= words[i].length <= 1000
words[i] 仅由小写字母组成
0 <= sum(words[i].length) <= 105

来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/concatenated-words
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
*/

#include <vector>
#include <string>
#include <algorithm>

using std::vector;
using std::string;
using std::sort;

// 字典树 + 深度优先搜索
struct Trie {
    bool isEnd{false};
    vector<Trie*> children = vector<Trie*>(26, nullptr);
    Trie() {}
};
class Solution {
public:
    vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
        vector<string> res;
        // 按照字符串长度递增排序
        sort(words.begin(), words.end(), [&](const string& a, const string& b) {
            return a.size() < b.size();
        });
        int n = words.size();
        for (int i{0}; i < n; ++i) {
            string word = words[i];
            if (word.empty()) {
                continue;
            }
            if (dfs(word, 0)) {
                res.emplace_back(word);
            } else {
                insert(word);
            }
        }
        return res;
    }
private:
    Trie* trie = new Trie();
    bool dfs(const string& word, int start) {
        int n = word.size();
        if (n == start) {
            return true;
        }
        Trie* node = trie;
        for (int i{start}; i < n; ++i) {
            char c = word[i];
            int index = c - 'a';
            node = node->children[index];
            if (node == nullptr) {
                return false;
            }
            if (node->isEnd) {
                if (dfs(word, i + 1)) {
                    return true;
                }
            }
        }
        return false;
    }
    void insert(const string& word) {
        int n = word.size();
        Trie* node = trie;
        for (int i{0}; i < n; ++i) {
            char c = word[i];
            int index = c - 'a';
            if (node->children[index] == nullptr) {
                node->children[index] = new Trie();
            }
            node = node->children[index];
        }
        node->isEnd = true;
    }
};

// 字典树 + 深度优先搜索 + 记忆化搜索
struct Trie {
    bool isEnd{false};
    vector<Trie*> children = vector<Trie*>(26, nullptr);
    Trie() {}
};
class Solution {
public:
    vector<string> findAllConcatenatedWordsInADict(vector<string>& words) {
        vector<string> res;
        // 按照字符串长度递增排序
        sort(words.begin(), words.end(), [&](const string& a, const string& b) {
            return a.size() < b.size();
        });
        int n = words.size();
        for (int i{0}; i < n; ++i) {
            string word = words[i];
            if (word.empty()) {
                continue;
            }
            vector<int> visited(word.size(), 0);
            if (dfs(word, 0, visited)) {
                res.emplace_back(word);
            } else {
                insert(word);
            }
        }
        return res;
    }
private:
    Trie* trie = new Trie();
    bool dfs(const string& word, int start, vector<int>& visited) {
        int n = word.size();
        if (n == start) {
            return true;
        }
        if (visited[start]) {
            return false;
        }
        visited[start] = true;
        Trie* node = trie;
        for (int i{start}; i < n; ++i) {
            char c = word[i];
            int index = c - 'a';
            node = node->children[index];
            if (node == nullptr) {
                return false;
            }
            if (node->isEnd) {
                if (dfs(word, i + 1, visited)) {
                    return true;
                }
            }
        }
        return false;
    }
    void insert(const string& word) {
        int n = word.size();
        Trie* node = trie;
        for (int i{0}; i < n; ++i) {
            char c = word[i];
            int index = c - 'a';
            if (node->children[index] == nullptr) {
                node->children[index] = new Trie();
            }
            node = node->children[index];
        }
        node->isEnd = true;
    }
};
